package com.mic.yzzn.plc.core.control;

import com.mic.yzzn.plc.common.control.RMI.PLCCollector;
import com.mic.yzzn.plc.common.control.datastructure.*;
import com.mic.yzzn.plc.common.ins.datastructure.PLCContactorNumber;
import com.mic.yzzn.plc.common.ins.datastructure.PLCDataRegisterNumber;
import com.mic.yzzn.plc.common.instructions.basic.PLCRequestInstruction;
import com.mic.yzzn.plc.common.toolUtils.PLCContactorCode;
import com.mic.yzzn.plc.common.toolUtils.PLCDataCode;
import com.mic.yzzn.plc.core.control.datastructure.PLCConfig;
import com.mic.yzzn.plc.core.control.datastructure.PLCStationConfig;
import com.mic.yzzn.plc.core.control.handler.HandlerFactory;
import com.mic.yzzn.plc.core.control.handler.PLCMissionHandler;
import com.mic.yzzn.plc.core.ins.toolUtils.ExceptionToString;
import com.mic.yzzn.plc.core.ins.toolUtils.PLCCCircleQueue;
import com.mic.yzzn.plc.core.plcevents.PLCCycleEvent;
import com.mic.yzzn.plc.core.plcevents.PLCRealtimeEvent;
import com.mic.yzzn.plc.core.plcevents.listener.PLCEventListener;
import org.apache.log4j.Logger;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 采集器的实现类
 */
public class PLCCollectorImpl extends UnicastRemoteObject implements PLCCollector, Runnable {
    /**
     * 采集器的线程
     */
    Thread thread;
    /**
     * Logger
     */
    private static Logger logger = Logger.getLogger(PLCCollectorImpl.class);

    /**
     * 网络传输时间
     */
    private volatile long time1 = 50;
    private volatile long time2 = 50;
    private volatile long time3 = 50;

    /**
     * 采集器开始运行的时间
     */
    private Date startTime;

    /**
     * 采集者的编号
     */
    private String collectorNumber;
    /**
     * 是否正在休眠
     */
    private volatile boolean asleep = false;
    /**
     * 是否已经停止
     */
    private volatile boolean stop = false;
    /**
     * 是否应该进行周期任务
     */
    private volatile boolean rest = false;
    /**
     * 当前周期任务的下标
     */
    private int cycleMissionIndex = 0;
    /**
     * 周期任务控制计时器
     */
    private Timer cycleFlashTimer;
    /**
     * 周期任务间隔时间
     */
    private long cycleMissionInterval = 300;
    /**
     * 任务处理器
     */
    PLCMissionHandler handler;

    /**
     * #0任务队列
     */
    private ConcurrentLinkedQueue<PLCMission> realtimeWriteQueue = new ConcurrentLinkedQueue<PLCMission>();
    /**
     * #1任务队列
     */
    private ConcurrentLinkedQueue<PLCMission> realtimeReadQueue = new ConcurrentLinkedQueue<PLCMission>();
    /**
     * #2任务队列
     */
    private PLCCCircleQueue<PLCMission> cycleMissionQueue;

    /**
     * #0队列结果
     */
    private ConcurrentHashMap<UUID, PLCMissionResult> realtimeWriteResults = new ConcurrentHashMap<UUID, PLCMissionResult>();
    /**
     * #1队列结果
     */
    private ConcurrentHashMap<UUID, PLCMissionResult> realtimeReadResults = new ConcurrentHashMap<UUID, PLCMissionResult>();
    /**
     * #2队列结果
     */
    private ConcurrentHashMap<String, PLCData> cycleMissionResults = new ConcurrentHashMap<String, PLCData>();

    /**
     * 废弃的实时任务结果ID列表
     */
    private ArrayList<UUID> discardedResultIDArray = new ArrayList<UUID>();
    /**
     * 每做该次数的实时任务后清理废弃结果
     */
    private int resultCleanInterval = 50;

    /**
     * 已经执行了的#0任务
     */
    private int realtimeWriteFinished = 0;
    /**
     * 已经执行了的#1任务
     */
    private int realtimeReadFinished = 0;
    /**
     * 已经执行了的#2任务
     */
    private int cycleMissionFinished = -1;

    /**
     * 实时任务事件监听者
     */
    private CopyOnWriteArrayList<PLCEventListener> realtimeEventListeners = new CopyOnWriteArrayList<PLCEventListener>();
    /**
     * 轮询任务事件监听者
     */
    private CopyOnWriteArrayList<PLCEventListener> cycleEventListeners = new CopyOnWriteArrayList<PLCEventListener>();

    /**
     * PLC任务配置
     */
    private PLCConfig config = new PLCConfig();
    /**
     * PLC收集器信息配置
     */
    private PLCStationConfig stationConfig;

    /**
     * 序列化ID
     */
    private static final long serialVersionUID = -4502698302184189069L;

    // 初始化方法
    protected PLCCollectorImpl() throws RemoteException {
        super();
    }

    public PLCCollectorImpl(PLCStationConfig stationConfig) throws RemoteException, Exception {
        super();
        setStationConfig(stationConfig);
        this.collectorNumber = stationConfig.id();
        // 初始化任务处理器
        handler = HandlerFactory.handler(stationConfig);
    }

    /**
     * 使采集器开始运作
     */
    public void startToWork() {
        if (!stop && thread != null) {
            return;
        }

        startTime = new Date();
        setStop(false);
        thread = new Thread(this);
        thread.start();
    }

    /**
     * 使采集器停止运作
     */
    public void stopWork() {
        setStop(true);
    }

    /**
     * 添加实时任务事件监听者
     *
     * @param listener 监听者
     */
    public void addRealtimeEventListener(PLCEventListener listener) {
        realtimeEventListeners.add(listener);
    }

    /**
     * 添加轮询任务事件监听者
     *
     * @param listener 监听者
     */
    public void addCycleEventListener(PLCEventListener listener) {
        cycleEventListeners.add(listener);
    }

    /**
     * 移除实时任务事件监听者
     *
     * @param listener 监听者
     * @return 是否移除成功
     */
    public boolean removeRealtimeEventListener(PLCEventListener listener) {
        return realtimeEventListeners.remove(listener);
    }

    /**
     * 移除周期轮询任务监听者
     *
     * @param listener 监听者
     * @return 是否移除成功
     */
    public boolean removeCycleEventListener(PLCEventListener listener) {
        return cycleEventListeners.remove(listener);
    }

    /**
     * RMI远程添加实时写任务
     */
    @Override
    public PLCMissionResult addRealtimeWriteMission(PLCMission mission) throws RemoteException {
        System.out.println("接收到实时写任务");
        if (mission.getPriority() != PLCMission.PLCMISSION_PRIORITY_RTW_0) {
            PLCMissionResult errorResult = new PLCMissionResult(UUID.randomUUID());
            errorResult.setResultType(PLCMissionResult.PLCResultMissionPriorityError);
            return errorResult;
        }

        return addMissionToRealtimeQueue(realtimeWriteQueue, mission);
    }

    /**
     * RMI远程添加实时读任务
     */
    @Override
    public PLCMissionResult addRealtimeReadMission(PLCMission mission) throws RemoteException {
        System.out.println("接收到实时读任务");
        if (mission.getPriority() != PLCMission.PLCMISSION_PRIORITY_RTR_1) {
            PLCMissionResult errorResult = new PLCMissionResult(UUID.randomUUID());
            errorResult.setResultType(PLCMissionResult.PLCResultMissionPriorityError);
            return errorResult;
        }

        return addMissionToRealtimeQueue(realtimeReadQueue, mission);
    }

    /**
     * RMI获取周期任务的结果
     */
    @Override
    public PLCMissionResult getCycleMissionResult() throws RemoteException {
        ArrayList<PLCData> resultArray = new ArrayList<PLCData>();

        for (Iterator<PLCData> i = cycleMissionResults.values().iterator(); i.hasNext(); ) {
            resultArray.add(i.next());
        }

        return new PLCMissionResult(UUID.randomUUID(), resultArray);
    }

    /**
     * RMI批量获取指定R点的数据
     *
     * @param numberArray R点数组
     * @return 对应的R点数据
     * @throws RemoteException
     */
    @Override
    public PLCMissionResult getCycleMissionResultFromContactorNumberArray(ArrayList<PLCContactorNumber> numberArray) throws RemoteException {
        ArrayList<PLCData> resultArray = new ArrayList<PLCData>();
        for (PLCContactorNumber cNumber : numberArray) {
            PLCData data = cycleMissionResults.get(cNumber.toString());

            // 如果结果中没有
            if (data == null) {
                data = new PLCData();
                data.setDataNumber(cNumber.toString());
                data.setData("null");
                data.setPointType(PLCContactorCode.PLC_INNER_RELAY_CODE);
                data.setState(PLCDataStatus.PLC_DATA_STATUS_NO_DATA);
                data.setTimestamp(new Date());
            }

            resultArray.add(data);
        }

        return new PLCMissionResult(UUID.randomUUID(), resultArray);
    }

    /**
     * RMI批量获取指定D点的数据
     *
     * @param numberArray D点数组
     * @return 对应的D点数据
     * @throws RemoteException
     */
    @Override
    public PLCMissionResult getCycleMissionResultFromDataRegisterNumberArray(ArrayList<PLCDataRegisterNumber> numberArray) throws RemoteException {
        ArrayList<PLCData> resultArray = new ArrayList<PLCData>();
        for (PLCDataRegisterNumber dNumber : numberArray) {
            int begin = Integer.parseInt(dNumber.getBeginBCDDataNumber());
            int end = Integer.parseInt(dNumber.getEndBCDDataNumber());

            for (; begin <= end; ++begin) {
                PLCData data = cycleMissionResults.get(String.valueOf(begin));

                // 如果结果中没有
                if (data == null) {
                    data = new PLCData();
                    data.setDataNumber(String.valueOf(begin));
                    data.setData("null");
                    data.setPointType(PLCDataCode.PLC_DATA_REGISTER_CODE);
                    data.setState(PLCDataStatus.PLC_DATA_STATUS_NO_DATA);
                    data.setTimestamp(new Date());
                }

                resultArray.add(data);
            }
        }

        return new PLCMissionResult(UUID.randomUUID(), resultArray);
    }

    // Getter
    public String getCollectorNumber() {
        return collectorNumber;
    }

    public int getCycleMissionFinished() {
        return cycleMissionFinished;
    }

    public boolean isStop() {
        return stop;
    }

    public boolean isAsleep() {
        return asleep;
    }

    public Date getStartTime() {
        return startTime;
    }

    public PLCStationConfig getStationConfig() {
        return stationConfig;
    }

    // Setter

    public void setCollectorNumber(String collectorNumber) {
        this.collectorNumber = collectorNumber;
    }


    /**
     * asleep = true;
     * asleep = false;
     *
     * @param asleep
     */
    public synchronized void setAsleep(boolean asleep) {
        this.asleep = asleep;

        if (!asleep) {
            notify();
        }
    }

    private synchronized void setStop(boolean stop) {
        this.stop = stop;

        if (stop && asleep) {
            notify();
        }
    }

    public void setConfig(PLCConfig config) {
        this.config = config;
    }

    /**
     * 替换stationConfig
     *
     * @param stationConfig
     */
    public void replaceStationConfig(PLCStationConfig stationConfig) {
        setAsleep(true);

        // 替换队列
        setStationConfig(stationConfig);
        // 清空旧缓存
        cycleMissionResults.clear();

        setAsleep(false);
    }

    /**
     * 取出stationConfig的更新日期
     *
     * @return
     */
    public java.util.Date stationConfigTimestamp() {
        return stationConfig.updateTime;
    }

    /**
     * Run方法
     */
    @Override
    public void run() {
        while (!stop) // 若没停止则循环执行 graceful stop
        {
            if (asleep) // 若休眠则进行休眠
            {
                try {
                    synchronized (this) {
                        wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    logger.error(ExceptionToString.exceptionToString(e));
                }
            } else {

                // 遍历实时写队列执行任务
                while (!realtimeWriteQueue.isEmpty()) {
                    for (int i = 0; i < realtimeWriteQueue.size(); ++i) {
                        PLCMission writeMission = realtimeWriteQueue.poll();
                        try {
                            handleWriteMission(writeMission);
                        } catch (Exception e) {
                            logger.error(ExceptionToString.exceptionToString(e));
                        }

                    }
                }

                // 遍历实时读队列执行任务
                while (!realtimeReadQueue.isEmpty()) {
                    for (int i = 0; i < realtimeReadQueue.size(); ++i) {
                        PLCMission readMission = realtimeReadQueue.poll();
                        try {
                            handleReadMission(readMission);
                        } catch (Exception e) {
                            e.printStackTrace();
                            logger.error(ExceptionToString.exceptionToString(e));
                        }
                    }
                }

                // 取出一个周期轮询任务并执行
                if (!rest && !cycleMissionQueue.isEmpty()) {
                    PLCMission cycleMission = cycleMissionQueue.getNext();

                    try {
                        handleCycleMission(cycleMission);
                    } catch (Exception e) {
                        e.printStackTrace();
                        logger.error(ExceptionToString.exceptionToString(e));
                    }


                    ++cycleMissionIndex;

                    // 判断是否已经执行完一个周期
                    if (cycleMissionIndex == cycleMissionQueue.size() && stationConfig.getFlashTime() > 0) {
                        // 记录周期结束时间
                        logger.debug(collectorNumber + "-end-" + new Date());
                        System.out.println("周期队列休眠");
                        // 让周期队列休眠并设置timer的任务
                        cycleMissionIndex = 0;
                        rest = true;
                        TimerTask task = new TimerTask() {
                            @Override
                            public void run() {
                                rest = false;
                                // 记录周期开始时间
                                logger.debug(collectorNumber + "-start-" + new Date());
                            }
                        };
                        cycleFlashTimer = new Timer(false);
                        cycleFlashTimer.schedule(task, stationConfig.getFlashTime() * 1000);
                    }
                }

                // 如果没有实时任务则休眠 100 毫秒以及清理废弃结果
                if (realtimeReadQueue.isEmpty() && realtimeWriteQueue.isEmpty()) {
                    // 实时任务达到一定的次数后清理废弃结果
                    if ((realtimeReadFinished + realtimeWriteFinished) % resultCleanInterval == 0) {
                        cleanRealtimeResult();
                    }

                    try {
                        Thread.sleep(cycleMissionInterval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        logger.error(ExceptionToString.exceptionToString(e));
                    }
                }
            }
        }
    }

    /**
     * 获取平均等待时长
     *
     * @return
     */
    private long getWaitTime() {
        return (time1 + time2 + time3) / 3;
    }

    /**
     * 更新等待时间
     *
     * @param newTime
     */
    private void updateWaitTime(long newTime) {
        time1 = time2;
        time2 = time3;
        time3 = newTime;
    }

    /**
     * stationConfig私有Set方法
     *
     * @param stationConfig
     */
    private void setStationConfig(PLCStationConfig stationConfig) {
        this.stationConfig = stationConfig;
        this.cycleMissionIndex = 0; // 清空记录下标
        addMissionsToCycleQueueFromInstructionArray(stationConfig.getInstructionList());
    }

    /**
     * 把指令数组中的指令转成任务加到任务队列中
     *
     * @param array 指令数组
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void addMissionsToCycleQueueFromInstructionArray(ArrayList array) {
        ArrayList<PLCMission> missionList = new ArrayList<PLCMission>();
        for (PLCRequestInstruction ins : (ArrayList<PLCRequestInstruction>) array) {
            // 设置任务
            PLCMission mission = new PLCMission(
                    PLCMission.PLCMissionTypeReadNormal,
                    PLCMission.PLCMISSION_PRIORITY_CYR_2);
            ArrayList<PLCRequestInstruction> insArray = new ArrayList<PLCRequestInstruction>();
            insArray.add(ins);
            mission.setInstructions(insArray);
            missionList.add(mission);
        }

        // 将任务添加到队列中
        cycleMissionQueue = new PLCCCircleQueue<PLCMission>(missionList);
    }

    /**
     * 处理实时写任务
     *
     * @param mission
     */
    private void handleWriteMission(PLCMission mission) {
        handler.handleWriteMission(mission, realtimeWriteResults, (int) getWaitTime());

        // 递增完成条数
        ++realtimeWriteFinished;

        // 发布事件
        notifyRealtimeEventListeners(true);
    }

    /**
     * 处理实时读任务
     *
     * @param mission
     */
    private void handleReadMission(PLCMission mission) {
        handler.handleReadMission(mission, realtimeReadResults, (int) getWaitTime());

        // 递增完成条数
        ++realtimeReadFinished;

        // 发布事件
        notifyRealtimeEventListeners(false);
    }

    /**
     * 处理周期轮询任务
     *
     * @param mission
     */
    private void handleCycleMission(PLCMission mission) {
        updateWaitTime(handler.handleCycleMission(mission, cycleMissionResults, (int) getWaitTime()));

        // 递增完成条数
        ++cycleMissionFinished;

        // 发布事件
        notifyCycleEventListeners();
    }

    /**
     * 将指定任务添加到指定队列中
     *
     * @param realQueue
     * @param mission
     * @return 任务结果
     */
    private PLCMissionResult addMissionToRealtimeQueue(ConcurrentLinkedQueue<PLCMission> realQueue, PLCMission mission) {
        PLCMissionResult result = new PLCMissionResult(mission.getMissionID());
        result.setResultType(PLCMissionResult.PLCResultTypeNetWorkFail);

        // 如果采集器正在休眠则返回采集器休眠的失败提示
        if (asleep) {
            result.setResultType(PLCMissionResult.PLCResultTypeCollectorAsleep);
            return result;
        }

        if (realQueue.size() >= stationConfig.maxRealtimeMission) {
            System.out.println("采集器忙");
            result.setResultType(PLCMissionResult.PLCResultTypeCollectorBusyFail);
            return result;
        }

        boolean addSucceed = realQueue.add(mission);
        ConcurrentHashMap<UUID, PLCMissionResult> resultMap = (realQueue == realtimeWriteQueue) ? realtimeWriteResults
                : realtimeReadResults;

        if (!addSucceed) {
            result.setResultType(PLCMissionResult.PLCResultTypeMissionInsertError);
            return result;
        }

        int times = 1;
        int watingTimes = 4 + mission.getTryToWriteTimes();
        boolean isAck = (mission.getMissionType() == PLCMission.PLCMissionTypeWriteFireAndAck);
        long waitTime = (getWaitTime() * mission.getInstructions().size());
        if (isAck) // 如果是确认型任务则要增加等待时间
        {
            waitTime *= 2;
        }
        while (times < watingTimes) {
            try {
                Thread.sleep((waitTime * times) + cycleMissionInterval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            result = resultMap.get(mission.getMissionID());

            if (result != null) {
                resultMap.remove(result.getResultID());
                return result;
            }

            ++times;
        }

        // 找不到结果则将该任务的ID记录下来方便日后清理废弃结果
        discardedResultIDArray.add(mission.getMissionID());
        System.out.println("找不到结果");
        return result;
    }

    /**
     * 清理实时任务的废弃结果
     */
    private void cleanRealtimeResult() {
        if (discardedResultIDArray.isEmpty()) {
            return;
        }

        for (UUID uuid : discardedResultIDArray) {
            realtimeReadResults.remove(uuid);
            realtimeWriteResults.remove(uuid);
        }
    }

    /**
     * 通知所有的实时事件监听者
     *
     * @param isWriteEvent
     */
    private void notifyRealtimeEventListeners(boolean isWriteEvent) {
        // 发布事件
        if (!realtimeEventListeners.isEmpty()) {
            ConcurrentLinkedQueue<PLCMission> queue;
            int finishedNumber;
            int priority;
            if (isWriteEvent) {
                queue = realtimeWriteQueue;
                finishedNumber = realtimeWriteFinished;
                priority = PLCMission.PLCMISSION_PRIORITY_RTW_0;
            } else {
                queue = realtimeReadQueue;
                finishedNumber = realtimeReadFinished;
                priority = PLCMission.PLCMISSION_PRIORITY_RTR_1;
            }

            PLCCollectorQueueStatus status = new PLCCollectorQueueStatus(
                    collectorNumber,
                    0,
                    queue.size(),
                    finishedNumber,
                    priority);
            PLCRealtimeEvent realtimeEvent = new PLCRealtimeEvent(this, status);

            // 通知监听者
            for (PLCEventListener listener : realtimeEventListeners) {
                listener.notifyRealtimeEvent(realtimeEvent);
            }
        }
    }

    /**
     * 通知所有的周期事件监听者
     */
    private void notifyCycleEventListeners() {
        if (!cycleEventListeners.isEmpty()) {
            PLCCollectorQueueStatus status = new PLCCollectorQueueStatus(
                    collectorNumber,
                    cycleMissionIndex + 1,
                    cycleMissionQueue.size(),
                    cycleMissionFinished,
                    PLCMission.PLCMISSION_PRIORITY_CYR_2);
            PLCCycleEvent cycleEvent = new PLCCycleEvent(this, status);

            // 通知监听者
            for (PLCEventListener listener : cycleEventListeners) {
                listener.notifyCycleEvent(cycleEvent);
            }
        }
    }
}
